Fine-grained task-level concurrency between launchers#6955
Merged
Conversation
Member
|
@lihaoyi This change touches the return-type signature of some modules, which could result in binary breakages downstream. Do we want to bump to |
Member
Author
|
@lefou yes let's plan for the next release including these changes to be 1.2.0. It's a big enough semantic change that even apart from binary compatibility it deserves a bigger version bump |
lihaoyi
added a commit
that referenced
this pull request
May 4, 2026
…e` (#7069) Follow up from #6955 `exclusive = true` makes the command exclusive only within this launcher, which is used for commands that you want to run single-threaded per launcher e.g. to avoid interleaving with other tasks in the terminal logs, whereas `globalExclusive = true` takes the global exclusive write lock which is useful for tasks which actually do global side effects that should not be run concurrently even with other launchers. Most `exclusive = true` tasks remain unchanged, but those like `clean`, `bspClean`, `scalafmt`, `init`, etc. are marked like `globalExclusive = true` as part of this PR Also includes some code to make sure we release the locks when doing nested evaluations, so e.g. `show` evaluating tasks uses the locking strategy of the tasks it evaluates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is intended to remove the global out/ folder lock in favor of finer grained per-task or per-bootstrap-phase read/write locks, with handling for the various "global" out/ folder files (e.g.
mill-chrome-profile.json,mill-profile.json,mill-console-tail, etc.) to preserve their usefulness in the presence of concurrent runs.Locking
During task execution, we take a read lock for each task, if it needs to be re-evaluated we then take a write lock, and when evaluation is done we keep the read lock to ensure it remains stable for use by downstream tasks until all tasks downstream have completed, at which point the read lock is released.
When evaluating meta-builds, we first take a read lock and then run selective execution as a non-task-executing way of checking if nothing changed. Only if selective execution detects changes do we take the write lock and evaluate that meta-level's tasks, and convert to a read lock after it is done so it remains stable while we use it.
This change is meant to only affect daemon mode, since we don't have reliable filesystem locking we can rely on for non-daemon mode (e.g. docker folder mounts don't always support locks properly).
mill-out-lockis turned into a process-level lock, such that either a singleMillDaemonMainprocess orMillNoDaemonMainprocess can claim it. If aMillDaemonMainclaims it then it can support multiple concurrent runs with fine-grained locking internally, but if aMillNoDaemonMainclaims it then only that one run can take effectSplitting Of Global State and Per-Launcher State
The various
out/mill-*files are turned into symlinks toout/mill-run-*/*files: theout/mill-*file pointing at the most recentout/mill-run-*/*file to be created, but the olderout/mill-run-*/*files kept around in case people need them (e.g. due to concurrent use)The old
RunnerStatewas split into a daemon-wideRunnerSharedStateand a per-launcherRunnerLaunchState. This lets us mostly re-use the immutableRunnerSharedStatebetween launchers, while keeping the per-launcher state separate.RunnerSharedStateonly gets updated under a meta-build write lockTask.Workersare shared between concurrent launchers; this is necessary for efficiency, to avoid e.g. spawning duplicateJvmWorkerMainorZincWorkerclassloaders that can be very expensive.MillMain0.main0no longer takes and returns theRunnerStateat the end of each execution, since that only works in a single-threaded environment. Instead it receives asharedStateas anAtomicReference[RunnerSharedState]that it can update at any time as the necessary state gets computedBSP Changes
With this PR, we are swapping the default back for BSP to share the same
out/folder as normal execution, as the fine grained locking should keep things from blocking or corrupting each otherReplaced the long-lived BSP evaluator cache and iteration loop with a BspBootstrapBridge such that each request bootstraps fresh evaluators per-call. This makes BSP requests behave like CLI commands, importantly behaving the same w.r.t. locking and concurrency (e.g. meta-build lock, task locks, etc.). We make some tweaks to the meta-build bootstrap process (e.g. using selective execution as a pre-filter) to try and mitigate the slowdown of going through the bootstrap process every BSP request where we did not before
BSP requests are now handled in a multithreaded fashion, sharing the same locking/concurrency model as concurrent CLI commands. This keeps BSP and CLI in sync, since this PR already adds concurrency support for the CLI, we also add it for BSP
The caching of
BspEvaluatorswhen the meta-build is broken is removed. It is hard to implement with the new architecture in the presence of concurrency, and anyway partial meta-build failures still generate partial BSP output and so e.g. editing thebuild.millfiles while they're broken should still workMisc